<?php
/**
 * NMAP Functionality class
 *
 * This class will allow someone to use
 * nmap throug a PHP Script
 *
 * Some Common Scan Types ('*' options require root privileges)
 * *-sS TCP SYN stealth port scan (default if privileged (root))
 * -sT TCP connect() port scan (default for unprivileged users)
 * *-sU UDP port scan
 * -sP ping scan (Find any reachable machines)
 * *-sF,-sX,-sN Stealth FIN, Xmas, or Null scan (experts only)
 * -sV Version scan probes open ports determining service & app names/versions
 * -sR RPC scan (use with other scan types)
 * Some Common Options (none are required, most can be combined):
 * -O Use TCP/IP fingerprinting to guess remote operating system
 * -p <range> ports to scan.  *Example range: 1-1024,1080,6666,31337
 * -F Only scans ports listed in nmap-services
 * -v Verbose. Its use is recommended.  *Use twice for greater effect.
 * -P0 Don't ping hosts (needed to scan www.microsoft.com and others)
 * -Ddecoy_host1,decoy2[,...] Hide scan using many decoys
 * -6 scans via IPv6 rather than IPv4
 * -T <Paranoid|Sneaky|Polite|Normal|Aggressive|Insane> General timing policy
 * -n/-R Never do D  *resolution/Always resolve [default: sometimes resolve]
 * -oN/-oX/-oG <logfile> Output normal/XML/grepable scan logs to <logfile>
 * -iL <inputfile> Get targets from file; Use '-' for stdin
 * *-S <your_IP>/-e <devicename> Specify source address or network interface
 * Example: nmap -v -sS -O www.my.com 192.168.0.0/16 '192.88-90.*.*'
 *
 * THIS IS NOT ESCAPING SHELL ARGS, IF YOU WANT TO USE THIS IN WEB ENVIRONMENT
 * PLEASE MAKE SURE YOU SECURE YOUR STUFF BIG TIME
 *
 * @package   System_Nmap
 * @author    David Coallier <davidc@agoraproduction.com>
 * @copyright 2007 Agoraproduction
 * @version $Id$
 *
 * @todo Make sure to use a command line package to handle the options
 *       passed to the constructor. 
 */
final class PEAR2_System_Nmap
{
    /**
     * PEAR2 nmap newline.
     *
     * This constant is used to output
     * a new return line in the terminal.
     *
     * @var string New line
     */
    const PEAR2_NMAP_NEWLINE = "\n";
    
    /**
     * Command line options
     *
     * These are the options from the
     * command line ($args[2].. [6]...
     *
     * @var array $_options The options
     */
    protected $_options = array();
    
    /**
     * Final command
     *
     * This is the final command to be executed
     * constructed correctly for nmap.
     *
     * @access private
     * @var    string   $_finalCommand The final command
     */
    private $_finalCommand = 'nmap ';
    
    /**
     * Constructor
     *
     * In the constructor you are passing the arguments
     * of the command line. php args.php -sY -sS -O 
     */
    public function __construct($commandArguments)
    {
        switch (count($commandArguments)) {
            case '1':
                $this->showHelp();
                exit;
                break;
            case '2':
                if (strstr($commandArguments[count($commandArguments) - 1], '-') ||
                    $commandArguments[count($commandArguments) - 1]) {
                    $this->showHelp();
                    exit;
                }
                break;
            default:
                break;
        }
        
        
        $tmpPosition = 0;
        $tmpCommand     = array();
        $tmpLastCommand = array();

        foreach ($commandArguments as $argument) {
            
            $tmpLastCommand[$tmpPosition] = $argument;
            
            if (strstr($argument, '-')) {
                $tmpCommand[$tmpPosition]     = $argument;
            }
            
            if ($tmpPosition > 1 && 
                isset($tmpLastCommand[$tmpPosition - 1]) &&
                strstr($tmpLastCommand[$tmpPosition - 1], '-') &&
                !strstr($tmpLastCommand[$tmpPosition], '-')) 
            {
                $tmpCommand[$tmpPosition - 1] .= ' ' . $argument;
            }

            $tmpPosition = $tmpPosition + 1;
            
        }

        $this->setOptions($tmpCommand);
        $this->setHostname($commandArguments[count($commandArguments) - 1]);

        $this->executeCommand();
    }

    /**
     * Set the hostname
     *
     * This sets the hostname of the scan to
     * execute against.
     *
     * @access protected
     * @param  string $hostname  The hostname to scan.
     * @see $this->_finalCommand
     */
    protected function setHostname($hostname) 
    {
        $this->_finalCommand .= ' ' . $hostname;
    }
    
    /**
     * Set the options
     *
     * This method will set the options to be executed in
     * in the shell command.
     *
     * @access protected
     * @param  array $options  The options in the command.
     * @see    $this->_options
     */
    protected function setOptions(Array $options)
    {
        $this->_finalCommand .= implode(' ', $options);
    }

    /**
     * Execute a command
     *
     * This function executes the nmap command
     * and returns the value of what the shell said
     *
     * @access protected
     * @see $this->_finalCommand
     */
    protected function executeCommand()
    {
        $returnValue = array();

        exec($this->_finalCommmand, $returnValue);

        print_r($returnValue);
    }

    /**
     * Show Help
     *
     * This function will show the help that nmap would usually show
     * however, I have removed a few features with extra arguments and
     * options to make sure the handling does not die.
     *
     * @access protected
     */
    protected function showHelp()
    {
        $help = 
            'PEAR2_System_Nmap 0.1.0 ' . self::PEAR2_NMAP_NEWLINE . 
            'Usage: nmap [Scan Type(s)] [Options] <hostname>' . self::PEAR2_NMAP_NEWLINE .
            'Some Common Scan Types (\'*\' options require root privileges)' . self::PEAR2_NMAP_NEWLINE .
            '* -sS TCP SYN stealth port scan (default if privileged (root))' . self::PEAR2_NMAP_NEWLINE .
            '  -sT TCP connect() port scan (default for unprivileged users)' . self::PEAR2_NMAP_NEWLINE .
            '* -sU UDP port scan' . self::PEAR2_NMAP_NEWLINE .
            '  -sP ping scan (Find any reachable machines)' . self::PEAR2_NMAP_NEWLINE .
            '* -sF,-sX,-sN Stealth FIN, Xmas, or Null scan (experts only)' . self::PEAR2_NMAP_NEWLINE .
            '  -sV Version scan probes open ports determining service & app names/versions' . self::PEAR2_NMAP_NEWLINE .
            '  -sR RPC scan (use with other scan types)' . self::PEAR2_NMAP_NEWLINE .
            'Some Common Options (none are required, most can be combined):' . self::PEAR2_NMAP_NEWLINE .
            '* -O Use TCP/IP fingerprinting to guess remote operating system' . self::PEAR2_NMAP_NEWLINE .
            '  -p <range> ports to scan.  Example range: 1-1024,1080,6666,31337' . self::PEAR2_NMAP_NEWLINE .
            '  -F Only scans ports listed in nmap-services' . self::PEAR2_NMAP_NEWLINE .
            '  -v Verbose. Its use is recommended.  Use twice for greater effect.' . self::PEAR2_NMAP_NEWLINE .
            '  -P0 Don\'t ping hosts (needed to scan www.microsoft.com and others)' . self::PEAR2_NMAP_NEWLINE .
            '* -Ddecoy_host1,decoy2[,...] Hide scan using many decoys' . self::PEAR2_NMAP_NEWLINE .
            '  -6 scans via IPv6 rather than IPv4' . self::PEAR2_NMAP_NEWLINE .
            '  -T <Paranoid|Sneaky|Polite|Normal|Aggressive|Insane> General timing policy' . self::PEAR2_NMAP_NEWLINE .
            '  -n/-R Never do DNS resolution/Always resolve [default: sometimes resolve]' . self::PEAR2_NMAP_NEWLINE .
            '* -S <your_IP>/-e <devicename> Specify source address or network interface' . self::PEAR2_NMAP_NEWLINE;

        echo $help;
    }
}
